package biolean.logic;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import biolean.AppCanvas;
import biolean.Biolean;
import biolean.Utils;
import biolean.commands.Command;
import biolean.files.BioFile;

public class Project
{
	public static final String XML_COMMAND = "command";
	public static final String XML_CONNECTION = "connection";
	public static final String XML_FILE 	= "file";
	public static final String XML_X 		= "x";
	public static final String XML_Y 		= "y";
	public static final String XML_TYPE		= "type";
	public static final String XML_ARG		= "argument";
	public static final String XML_VAL		= "value";
	public static final String XML_NAME		= "name";
	public static final String XML_DESC		= "desc";
	public static final String XML_NULL		= "null";
	public static final String XML_READY	= "ready";
	public static final String XML_PATH		= "path";
	public static final String XML_TRUE		= "true";
	public static final String XML_FALSE	= "false";
	public static final String XML_DATE		= "date";
	public static final String XML_FROM		= "from";
	public static final String XML_TO		= "to";
	public static final String XML_SELECTED = "selected";
	
	private List<GraphicElement> mNodes = new ArrayList<GraphicElement>();
	private HashSet<GraphicElement> visited = new  HashSet<GraphicElement>();
	
	public GraphicElement get(int i){return mNodes.get(i);}	
	public int size(){return mNodes.size();}

	private ArrayList<Connection> nConnections;
	private GraphicElement mSelected = null;
	private AppCanvas mCanvas;
	private String mFilePath = null;
	
	
	
	private ArrayList<String> mFilename = new ArrayList<String>();
	
	
	
	public String generateFilename(BioFile file, String filePath)
	{
		String result = "";
		String ext = Biolean.fileRegister.findExtension( file.getClass() ) ;
		if (ext != null)
		{
			ext = "."+ext;
			if (filePath.contains(ext) )
			{
				filePath = filePath.substring(0, filePath.lastIndexOf(ext) );
			}
			int index = 1;
			while (mFilename.contains( filePath + ext ) )
			{
				if (index > 1)
				{
					filePath = filePath.substring(0, filePath.length() - 1);
				}
				filePath += Integer.toString( index );
				index++;
			}
			result = filePath + ext;
		}
		return result;
	}
	
	
	public void SelectFirst(GraphicElement element)
	{
			if (mSelected != null)
			{
				mSelected.setSelected(false);
			}
			mSelected = element;
			if (mSelected != null)
			{
				mSelected.setSelected( true );
			}
	}
	
	
	public Project(ArrayList<Connection> connections, AppCanvas canvas)
	{
		nConnections = connections;	
		mCanvas =  canvas;
	}
	
	
	public Command getFirstCommand()
	{
		for (GraphicElement el : mNodes )
		{
			if ( Utils.isCommand( el ) )
			{
				return (Command) el;
			}
		}
		return null;
	}
	
	public void add(GraphicElement element)
	{
		mNodes.add(element);
		if ( Utils.isBioFile(element) )
		{
			BioFile f  = (BioFile) element;
			mFilename.add( f.getFilePath() );
		}
		
		if (mNodes.size() == 1)
		{
			SelectFirst( element );
		}
	}
	
	public boolean connect(GraphicElement source, GraphicElement target)
	{
		if ( Utils.isBioFile( target ) && Utils.isCommand(source  ) )
		{
			return connect((Command)source, (BioFile)target );
		}
		else if ( Utils.isCommand( target ) && Utils.isBioFile(source  ) )
		{
			return connect((BioFile)source, (Command)target );
		}
		return false;
	}
	
	private boolean connect(BioFile source, Command target)
	{
		source.setIOType(BioFile.IOTYPE.INPUT);
		boolean b =  source.connectToCommand(target);
		return b;
	}
	
	private boolean connect(Command source, BioFile target)
	{
		target.setIOType(BioFile.IOTYPE.OUTPUT);
		boolean b =  source.connectFile(target);
		return b;
	}

	
	private boolean disconnect(BioFile source, Command target)
	{
		source.setIOType(BioFile.IOTYPE.INPUT);
		return source.disconnectFromCommand(target);
	}
	
	private boolean disconnect(Command source, BioFile target)
	{
		target.setIOType(BioFile.IOTYPE.OUTPUT);
		return source.disconnectFile(target);
	}
	
	
	public boolean canDisconnect(Connection connection)
	{
		if (Utils.isBioFile( connection.inputElem ) )
		{
			BioFile file = (BioFile) connection.inputElem;
			return file.isReady();
		}
		return true;
	}
	
	public boolean disconnect(GraphicElement source, GraphicElement target)
	{
		if ( Utils.isBioFile( target ) && Utils.isCommand( source ) )
		{
			return disconnect((Command)source, (BioFile)target);
		}
		else if ( Utils.isBioFile( source ) && Utils.isCommand( target ) )
		{
			return disconnect((BioFile)source, (Command)target);
		}
		return false;
	}
	
	public boolean canRemove(GraphicElement element)
	{
		if (Utils.isBioFile( element ))
		{
			BioFile file = (BioFile) element;
			return file.isReady();
		}
		return true;
	}
	
	public boolean remove(GraphicElement node)
	{
		if (node == mSelected)
		{
			mSelected = null;
		}
		if ( Utils.isBioFile( node ) )
		{
			return remove((BioFile)node);
		}
		else if ( Utils.isCommand( node ) )
		{
			return remove((Command)node);
		}
		return false;
	}
	
	public ArrayList<GraphicElement> getNotReadyElement(Command node)
	{
		ArrayList<GraphicElement> toRemove = new ArrayList<GraphicElement>();
		
		for (GraphicElement element : node.getNeighbours() )
		{
			if (!element.isReady() )
			{
				toRemove.add(element);
			}
		}
		
		return toRemove;
	}
	
	private boolean remove(Command node)
	{
		disconnectNodeFromAll(node);
		mNodes.remove(node);
		return true;
	}
	
	private boolean remove(BioFile node)
	{
		disconnectNodeFromAll(node);
		mNodes.remove(node);
		return true;
	}
	 
	private void disconnectNodeFromAll( GraphicElement node)
	{

		while ( node.getNeighbours().size() > 0 )	
		{
			disconnect(node, node.getNeighbours().get(0) );
		}		
		while ( node.getInputs().size() >  0 )
		{
			disconnect(node.getInputs().get(0), node);
		}
	}
	
	public void execute()
	{
		visited.clear();
		if (mSelected != null)
		{
			Biolean.PrintToLog("_________________________________________________");
			Biolean.PrintToLog("Executing started...");
			doHlbky( mSelected);
			SaveToXML( mFilePath );
			Biolean.PrintToLog("Project autosave...");
		}
		else
		{
			Biolean.PrintToLog("Could not start beacuse of missing start command");
			Biolean.PrintToLog("You can select command by  'ctrl + left click' ");
		}
	}
	
	public void doHlbky(GraphicElement element)
	{
		visited.add(element);
		if (Utils.isCommand( element ) )
		{
			Command cmd = (Command) element;
			if (cmd.isReady() )
			{			
				try{
					cmd.processArguments();
					cmd.run();
					for ( GraphicElement el : cmd.getNeighbours())
					{
						((BioFile)el).UpdateDate();
						el.setReady(true);
					}
					for (GraphicElement el : cmd.getInputs() ){
						((BioFile)el).UpdateDate();
					}
					mCanvas.repaint();
				} catch (IOException e){
					e.printStackTrace();
				}
			}
			else
			{
				Biolean.PrintToLog("Command " + cmd.getName() +  " is not ready for executing...STOPED");
			}
		}
		
		for (GraphicElement neighbour : element.getNeighbours() )
		{
			if (!visited.contains(neighbour) )
			{
				doHlbky( neighbour ); 
			}
		}
	}
	
	private void mCheckFileDates(GraphicElement element, boolean parentOk)
	{
		if (Utils.isBioFile(element) )
		{
			BioFile file = (BioFile) element;
			if (file.exists() && file.isRoot() )
				element.setReady(true);
		}
		
		if (element.isReady() || Utils.isCommand(element) )
		{
			boolean rekParentOk = parentOk;
			if (Utils.isSubClass( BioFile.class, element.getClass() ) )
			{
				BioFile file = (BioFile)element;
				
				boolean oldOk = rekParentOk;			
				rekParentOk = rekParentOk && file.DateOK();
				if (oldOk && !rekParentOk)
					Biolean.PrintToLog("The date of '" + file.getFilePath() + "' file is not correct, please run your project again");
				if (!file.exists() ){
					file.setReady(false);
					Biolean.PrintToLog("File '" + file.getFilePath() + "' doesn't exist");
				}
				else if (!rekParentOk && file.isRoot() ){
					file.setReady(true);
					
				}
				else{
					file.setReady(rekParentOk);
				}
			}
			for (GraphicElement neighbour : element.getNeighbours())
			{
				if (!visited.contains(neighbour) )//TODO:: to logovanie urobit inde...pretoze to toho tam ti vlezie iba ak je file
				{
					mCheckFileDates( neighbour ,rekParentOk );
				}
			}
		}
	}
	
	public void CheckFileDates()
	{		
		//Biolean.PrintToLog("Checking file dates...");
		for (GraphicElement node : mNodes )
		{
			if (Utils.isBioFile( node ) && node.getInputs().size() == 0 ) // ak file nema ziadnych predkov
			{
				visited.clear();
				mCheckFileDates(node, true);
				System.out.println("mCheckFileDates");
			}
		}
	}
	
	private void SaveData(Document doc) throws ParserConfigurationException, TransformerException
	{
		ArrayList<Element> xmlConnections = new ArrayList<Element> ();
		Element root = doc.createElement("project");
		for (GraphicElement node : mNodes)
		{
			for (GraphicElement neighbour : node.getNeighbours() )
			{
				Element connectionElemenet = doc.createElement(XML_CONNECTION); //this, connection najdeny
				int indexFrom = Utils.findIndex(mNodes ,node );
				int indexTo  = Utils.findIndex(mNodes, neighbour );
				
				Attr from = doc.createAttribute(XML_FROM);
				Attr to   = doc.createAttribute(XML_TO);
				from.setValue( Integer.toString( indexFrom ) );	
				to.setValue( Integer.toString( indexTo   ) );	
				connectionElemenet.setAttributeNode(from);
				connectionElemenet.setAttributeNode(to);
				
				xmlConnections.add( connectionElemenet );
			}
			node.addToXML(doc, root);
		}
		for ( Element xmlConnection : xmlConnections)
		{
			root.appendChild( xmlConnection );
		}
		
		
		Element selectedCommand = doc.createElement(XML_SELECTED);
		String str = mSelected == null ? XML_NULL : Integer.toString(mNodes.indexOf(mSelected) );
		selectedCommand.setTextContent( str );
		root.appendChild(selectedCommand);
		
		doc.appendChild(root);
	}
	
	public void SaveToXML(String filename)
	{
		mFilePath = filename;
		
		CheckFileDates();
		DocumentBuilderFactory docFactor = DocumentBuilderFactory.newInstance();
		try
		{
			DocumentBuilder db = docFactor.newDocumentBuilder();
			Document doc = db.newDocument();
			
			SaveData(doc);
	        
			StreamResult xmlOutput = new StreamResult( new FileWriter(filename) );
	        TransformerFactory transformerFactory = TransformerFactory.newInstance();
	        transformerFactory.setAttribute("indent-number", 4);
	        Transformer transformer = transformerFactory.newTransformer(); 
	        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
	        DOMSource source = new DOMSource(doc);
	        transformer.transform(source, xmlOutput);			
		} catch (ParserConfigurationException e){
			e.printStackTrace();
		} catch (TransformerConfigurationException e){
			e.printStackTrace();
		}catch (TransformerException e){
			e.printStackTrace();
		} catch (IOException e){
			e.printStackTrace();
		}
	}

	private void loadConnectionFromXML(Node node)
	{
		int from = Integer.parseInt( node.getAttributes().getNamedItem(XML_FROM).getTextContent() ); 
		int to   = Integer.parseInt( node.getAttributes().getNamedItem(XML_TO).getTextContent() );
		connect( get(from), get(to) );
		Connection connection = new Connection( get(from), get(to) );
		nConnections.add(connection);
	}
	
	public void LoadFromXML(String filename)
	{
		mFilePath = filename;
		
		try 
		{
			mNodes.clear();
			nConnections.clear();
			
			File fXmlFile = new File(filename);
			DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
			Document doc = dBuilder.parse(fXmlFile);
			doc.getDocumentElement().normalize();
			NodeList rootChildren = doc.getDocumentElement().getChildNodes();
		
			for (int i = 0 ; i < rootChildren.getLength(); i++)
			{
				GraphicElement addElement = null;
				Node node = rootChildren.item(i);
				String nodeName = node.getNodeName();
				if (nodeName.equals( XML_COMMAND) ){
					addElement = Command.loadFromXML(node);
				}else if (nodeName.equals( XML_FILE) ){
					addElement = BioFile.loadFromXML(node);
				}else if (nodeName.equals( XML_CONNECTION) ){//toto mozeme pretoze connections su az sa nodami
					loadConnectionFromXML(node);
				}else if (nodeName.equals( XML_SELECTED ) ){
					String tmp = node.getTextContent();
					GraphicElement el = tmp.equals(XML_NULL) ? null :  get( Integer.parseInt( tmp ) );
					SelectFirst(el);
				}
				if (addElement != null){
					add(addElement);
				}
			}

			
			CheckFileDates();
			mCanvas.repaint();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void clear()
	{
		mNodes.clear();
		nConnections.clear();
		visited.clear();
		mSelected = null;
	}
}
